package vh.sphynx.runtime.pattern;

import vh.sphynx.runtime.Converter;
import vh.sphynx.runtime.ExecutionContext;


@Converter(converter = { PatternMatchInfoTypeConverter.class })
public class PatternMatchInfo implements IPatternMatchInfo {
	
	private CharSequence input, localInput;
	private boolean localInputDirty;
	private int offset = 0;
	private int position = 0;
	private int beginPos = -1;
	private int endPos = -1;
	protected ExecutionContext context;
	private boolean matched = true;
	private Object returnedResult;
	private boolean useParentInput;
	private int occurence;
	//private Map<Object, Object>  options;
	 
	public final static PatternMatchInfo FALSE_MATCHED;
	
	static {
		FALSE_MATCHED = new PatternMatchInfo();
		FALSE_MATCHED.setMatched(false);
	}
	
	public PatternMatchInfo() {
		
	}
	
	@Override
	public String toString() {
		return String.format("offset: %d, begin=%d, end=%d, occurence=%d", 
				getGlobalOffset(),
				getGlobalBeginPosition(),
				getGlobalEndPosition(),
				getOccurrenceIndex());
	}
	
	@Override
	public CharSequence getGlobalInput() {
		return this.input;
	}
	
	@Override
	public void setGlobalInput(CharSequence input) {
		this.input = input;
	}

	@Override
	public CharSequence getLocalInput() {
		if (isLocalInputDirty() && input != null) {
			int newOffset = getGlobalBeginPosition();
			localInput = input.subSequence(getGlobalBeginPosition(), getGlobalEndPosition());
			offset = newOffset;
			beginPos = 0;
			endPos = localInput.length();
		}
		return localInput;
	}

	protected boolean isLocalInputDirty() {
		return localInputDirty || localInput == null;
	}
	
	@Override
	public int getGlobalOffset() {
		return offset;
	}
	
	@Override
	public void setGlobalOffset(int value) {
		offset = value;
		localInputDirty = true;
	}
	
	@Override
	public int getPosition() {
		return Math.max(position, 0);
	}
	
	@Override
	public int getGlobalPosition() {
		return getPosition() + getGlobalOffset();
	}

	@Override
	public int getGlobalEndPosition() {
		return getEndLocation() + getGlobalOffset();
	}
	
	public int getGlobalBeginPosition() {
		return getBeginLocation() + getGlobalOffset();
	}
	
	@Override
	public int setPosition(int value, int mode) {
		switch (mode) {
		case SEEK_LOCAL_FROM_BEGIN: // from begin
			return position = Math.min(value, input.length());
		case SEEK_GLOBAL_FROM_BEGIN: 
			return position = Math.max(value - getBeginLocation(), 0);
		case SEEK_RELATIVE:
			return position = Math.min(position + value, input.length());
		case SEEK_FROM_END:
			return position = Math.max(0, input.length() - value);
		}
		return position;
	}

	@Override
	public int getBeginLocation() {
		return Math.max(this.beginPos, 0);
	}
	
	@Override
	public void setBeginLocation(int value) {
		if (value != getBeginLocation()) {
			localInputDirty = true;
			beginPos = value;
			getLocalInput();
		}
	}

	@Override
	public int getEndLocation() {
		return Math.max(endPos != -1 || input == null ? this.endPos : input.length() - getGlobalOffset(), getPosition());
	}

	@Override
	public void setEndLocation(int value) {
		if (value != getEndLocation()) {
			localInputDirty = true;
			this.endPos = value;
			getLocalInput();
		}
	}

	public ExecutionContext getContext() {
		return context;
	}

	public void setContext(ExecutionContext context) {
		this.context = context;
	}

	public void setMatched(boolean matched) {
		this.matched = matched;
	}

	public boolean isMatched() {
		return matched;
	}

	@Override
	public void updateEndLocation(int value) {
		setEndLocation(Math.max(value, getEndLocation()));
	}
	
	@Override
	public int getMatchedLength() {
		return isMatched() ? getEndLocation() - getBeginLocation() : 0;
	}

	@Override
	public void intialize() {
		//options = getContext().getVariableValue("options", HashMapEx.class);
	}

	@Override
	public void setReturnedResult(Object value) {
		this.returnedResult = value;
	}

	@Override
	public Object getReturnedResult() {
		return returnedResult;
	}

	@Override
	public boolean isUseParentInput() {
		return this.useParentInput;
	}

	@Override
	public void setUseParentInput(boolean value) {
		this.useParentInput = value;
	}

	@Override
	public int getOccurrenceIndex() {
		return this.occurence;
	}

	@Override
	public void setOccurrenceIndex(int value) {
		this.occurence = value;
	}	
	
	protected <T> T getOption(String key, T defaultValue) {
		/*Object result;
		if (options != null && options.containsKey(key))
			result = options.get(key);
		else
			result = defaultValue;
		return (T)result;*/
		return context.getVariableValueWithDefault(key, defaultValue);
	}
}
